<?php

namespace app\sysuser\library;

use app\common\enums\UserStatusEnum;
use app\common\facade\Token;
use app\common\model\banxi\sys\SysLogininfor;
use app\common\model\banxi\sys\SysMenu;
use app\common\model\banxi\sys\SysRole;
use app\common\model\banxi\sys\SysRoleMenu;
use app\common\model\banxi\sys\SysUser;
use ba\Random;
use think\facade\Cache;
use think\facade\Config;
use Throwable;

/**
 * 管理员权限类
 * @property int $id         管理员ID
 * @property string $username   管理员用户名
 * @property string $nickname   管理员昵称
 * @property string $email      管理员邮箱
 * @property string $mobile     管理员手机号
 */
class Auth extends \ba\Auth
{
    /**
     * token 入库 type
     */
    public const TOKEN_TYPE = 'sys_user';

    /**
     * 是否登录
     * @var bool
     */
    protected bool $loginEd = false;

    /**
     * 错误消息
     * @var string
     */
    protected string $error = '';

    /**
     * Model实例
     * @var ?SysUser
     */
    protected ?SysUser $model = null;

    /**
     * 令牌
     * @var string
     */
    protected string $token = '';

    /**
     * 刷新令牌
     * @var string
     */
    protected string $refreshToken = '';

    /**
     * 令牌默认有效期
     * 可在 config/buildadmin.php 内修改默认值
     * @var int
     */
    protected int $keepTime = 86400;


    public function __construct(array $config = [])
    {
        parent::__construct($config);
        $this->setKeepTime((int)Config::get('buildadmin.admin_token_keep_time'));
    }

    /**
     * 魔术方法-管理员信息字段
     * @param $name
     * @return mixed 字段信息
     */
    public function __get($name): mixed
    {
        return $this->model?->$name;
    }

    /**
     * 初始化
     * @access public
     * @param array $options 传递到 /ba/Auth 的配置信息
     * @return Auth
     */
    public static function instance(array $options = []): Auth
    {
        $request = request();
        if (!isset($request->sysuserAuth)) {
            $request->sysuserAuth = new static($options);
        }
        return $request->sysuserAuth;
    }

    /**
     * 根据Token初始化管理员登录态
     * @param string $token
     * @return bool
     * @throws Throwable
     */
    public function init(string $token): bool
    {
        $tokenData = Token::get($token);
        if ($tokenData) {

            /**
             * 过期检查，过期则抛出 @see TokenExpirationException
             */
            Token::tokenExpirationCheck($tokenData);

            $userId = intval($tokenData['user_id']);
            if ($tokenData['type'] == self::TOKEN_TYPE && $userId > 0) {
                $this->model = SysUser::where('id', $userId)->find();
                if (!$this->model) {
                    $this->setError('Account not exist');
                    return false;
                }
                if ($this->model['status'] != UserStatusEnum::NORMAL) {
                    $this->setError('Account disabled');
                    return false;
                }
                $this->token = $token;
                $this->loginSuccessful();
                return true;
            }
        }
        $this->reset();
        $this->setError('Token login failed');
        return false;
    }

    /**
     * 管理员登录
     * @param string $username 用户名
     * @param string $password 密码
     * @param bool $keep 是否保持登录
     * @return bool|array
     * @throws Throwable
     */
    public function login(string $username, string $password)
    {
        $model = new SysUser();
        //判断登录错误次数
        if (!$model->validateLimit($username, self::TOKEN_TYPE)) {
            $this->setError('登录失败次数过多，请稍后重试');
            return false;
        }
        $this->model = $model->where('userName', $username)->find();
        if (!$this->model) {
            $this->setError('Account not exist');
            return false;
        }
        if ($this->model->status == UserStatusEnum::DISABLE) {
            $this->setError('Account disabled');
            return false;
        }
        $passwordCheck = verifyMd5Password($password, $this->model->password, $this->model->salt);
        $this->model->recordError($passwordCheck, $username, self::TOKEN_TYPE, '用户名或密码错误');

        $this->clearToken();
        $sysLogininfor = new SysLogininfor();
        $sysLogininfor->recordLogininfor($this->model->id, $this->model->userName, self::TOKEN_TYPE, SysLogininfor::LOGIN_SUCCESS, '登录成功');
        $this->loginSuccessful();
        return [
            'token' => $this->token,
            'userinfo' => $this->getInfo(),
        ];
    }

    /**
     * 清理Token
     * @return bool
     */
    public function clearToken(): bool
    {
        // 清理 token
        if (Config::get('buildadmin.sys_user_sso')) {
            Token::clear(self::TOKEN_TYPE, $this->model->id);
            Token::clear(self::TOKEN_TYPE . '-refresh', $this->model->id);
        }
        return true;
    }

    /**
     * 管理员登录成功
     * @return bool
     */
    public function loginSuccessful(): bool
    {
        if (!$this->model) return false;
        $this->model->startTrans();
        try {
            $this->model->loginIp = request()->ip();
            $this->model->loginDate = date('Y-m-d H:i:s');
            $this->model->save();
            $this->loginEd = true;
            if (!$this->token) {
                $this->setToken();
            }

            $this->model->commit();
        } catch (Throwable $e) {
            $this->model->rollback();
            $this->setError($e->getMessage());
            return false;
        }
        return true;
    }


    /**
     * 设置Token
     * @return bool
     */
    public function setToken(): bool
    {
        $this->token = Random::uuid();
        Token::set($this->token, self::TOKEN_TYPE, $this->model->id, $this->keepTime);
        return true;
    }

    /**
     * 退出登录
     * @return bool
     */
    public function logout(): bool
    {
        if (!$this->loginEd) {
            $this->setError('You are not logged in');
            return false;
        }
        return $this->reset();
    }

    /**
     * 是否登录
     * @return bool
     */
    public function isLogin(): bool
    {
        return $this->loginEd;
    }

    /**
     * 获取管理员模型
     * @return SysUser
     */
    public function getAdmin(): SysUser
    {
        return $this->model;
    }

    /**
     * 获取管理员Token
     * @return string
     */
    public function getToken(): string
    {
        return $this->token;
    }


    /**
     * 获取管理员信息 - 只输出允许输出的字段
     * @return array
     */
    public function getInfo()
    {
        if (!$this->model) return [];
        $info = $this->model;
        $info['permissions'] = $this->getPermissions($info['roleId']);
        $sysRoleModel = new SysRole();
        $info['roleInfo'] = $sysRoleModel->where(['id' => $info['roleId']])->find();
        return $info;
    }

    public function getPermissions($roleId)
    {
        $key = 'menu_list_by_roleId_' . $roleId;
        if (Cache::has($key)) {
            return Cache::get($key);
        }
        $menuList = [];
        $sysMenu = new SysMenu();
        if (SysRole::ROLE_ID == $roleId) {
            $list = $sysMenu->listByInId();
            foreach ($list as $item) {
                $menuList[] = [
                    'id' => $item['id'],
                    'menuName' => $item['menuName'],
                    'perms' => $item['perms']
                ];

            }
        } else {
            $sysRoleMenu = new SysRoleMenu();
            $menuIds = $sysRoleMenu->listMenuIdByRoleId($roleId);
            $pMenuIds = [];
            if ($menuIds) {
                // 优化后的代码
                foreach ($menuIds as $menuId) {
                    $menuInfo = $sysMenu->where('id', $menuId)->field('id,menuName,ancestors,perms')->find();
                    if (!$menuInfo) {
                        continue;
                    }
                    $menuList[] = [
                        'id' => $menuInfo['id'],
                        'menuName' => $menuInfo['menuName'],
                        'perms' => $menuInfo['perms']
                    ];
                    // 收集祖先菜单ID
                    if ($menuInfo['ancestors']) {
                        $ancestors = explode(',', $menuInfo['ancestors']);
                        $pMenuIds = array_merge($pMenuIds, $ancestors);
                    }
                }

                // 过滤需要排除的菜单ID（从祖先菜单中移除已在菜单列表中的ID和根节点0）
                $menuListIds = array_column($menuList, 'id');
                $pMenuIds = array_diff($pMenuIds, $menuListIds, [0]);
                $pMenuIds = array_unique($pMenuIds);

                // 获取祖先菜单信息
                if ($pMenuIds) {
                    $pMenuList = $sysMenu->listByInId($pMenuIds);
                    foreach ($pMenuList as $item) {
                        $menuList[] = [
                            'id' => $item['id'],
                            'menuName' => $item['menuName'],
                            'perms' => $item['perms']
                        ];
                    }
                }

            }
        }
        $perms = array_column($menuList, 'perms');
        Cache::set($key, $perms, 60 * 30);
        return $perms;
    }

    /**
     * @return string
     */
    public function getUserId()
    {
        return $this->model->id;
    }


    /**
     * 设置Token有效期
     * @param int $keepTime
     * @return void
     */
    public function setKeepTime(int $keepTime = 0): void
    {
        $this->keepTime = $keepTime;
    }

    /**
     * 设置错误消息
     * @param $error
     * @return Auth
     */
    public function setError($error): Auth
    {
        $this->error = $error;
        return $this;
    }

    /**
     * 获取错误消息
     * @return string
     */
    public function getError(): string
    {
        return $this->error ? __($this->error) : '';
    }

    /**
     * 属性重置（注销、登录失败、重新初始化等将单例数据销毁）
     */
    protected function reset(bool $deleteToken = true): bool
    {
        if ($deleteToken && $this->token) {
            Token::delete($this->token);
        }

        $this->token = '';
        $this->loginEd = false;
        $this->model = null;
        $this->refreshToken = '';
        $this->setError('');
        $this->setKeepTime((int)Config::get('buildadmin.sys_user_token_keep_time'));
        return true;
    }
}